/*
 * 
 *  This file is part of Sourceprojects.org Lycia Copyright 2010 noctarius
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 */
package org.sourceprojects.lycia.internal.generator;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.sourceprojects.lycia.exceptions.LyciaConfigurationException;

public class ParameternameAnalyserUtils {

	private ParameternameAnalyserUtils() {
	}

	public static final Parameter[] getParameter(Method method) {
		Class<?> declaringClass = method.getDeclaringClass();

		try {
			StaticClassVisitor scv = new StaticClassVisitor(method);

			InputStream bytecode = getClassAsStream(declaringClass);
			ClassReader classReader = new ClassReader(bytecode);

			classReader.accept(scv, 0);

			return scv.getParameters();
		} catch (IOException e) {
			throw new LyciaConfigurationException(
					"While trying to retrieve parameternames from class "
							+ declaringClass.getCanonicalName()
							+ " the class was not found", e);
		}
	}

	private static InputStream getClassAsStream(Class<?> clazz) {
		ClassLoader classLoader = clazz.getClassLoader();

		if (classLoader == null) {
			classLoader = ClassLoader.getSystemClassLoader();
		}

		return getClassAsStream(classLoader, clazz.getName());
	}

	private static InputStream getClassAsStream(ClassLoader classLoader,
			String className) {
		String name = className.replace('.', '/') + ".class";

		InputStream is = classLoader.getResourceAsStream(name);

		if (is == null) {
			is = ParameternameAnalyserUtils.class.getResourceAsStream(name);
		}

		return is;
	}

	private static class StaticClassVisitor implements ClassVisitor {
		private final Method method;

		private Parameter[] parameters = null;

		public StaticClassVisitor(Method method) {
			this.method = method;
		}

		@Override
		public void visit(int version, int access, String name,
				String signature, String superName, String[] interfaces) {
		}

		@Override
		public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
			return null;
		}

		@Override
		public void visitAttribute(Attribute attr) {
		}

		@Override
		public void visitEnd() {
		}

		@Override
		public FieldVisitor visitField(int access, String name, String desc,
				String signature, Object value) {
			return null;
		}

		@Override
		public void visitInnerClass(String name, String outerName,
				String innerName, int access) {
		}

		@Override
		public MethodVisitor visitMethod(int access, String name, String desc,
				String signature, String[] exceptions) {

			if (parameters != null) {
				return null;
			}

			if (!method.getName().equals(name)) {
				return null;
			}

			Type[] argumentTypes = Type.getArgumentTypes(desc);
			Type[] parameterTypes = Type.getArgumentTypes(method);

			if (argumentTypes.length != parameterTypes.length) {
				return null;
			}

			for (int i = 0; i < argumentTypes.length; i++) {
				if (!argumentTypes[i].equals(parameterTypes[i])) {
					return null;
				}
			}

			int parameterCount = argumentTypes.length;
			parameters = new Parameter[parameterCount];

			return new StaticMethodVisitor(method, this, parameterCount);
		}

		@Override
		public void visitOuterClass(String owner, String name, String desc) {
		}

		@Override
		public void visitSource(String source, String debug) {
		}

		Parameter[] getParameters() {
			return parameters;
		}

	}

	private static class StaticMethodVisitor implements MethodVisitor {
		private final StaticClassVisitor scv;
		private final int parameterCount;
		private final Annotation[][] annotations;
		private final Class<?>[] parameterTypes;

		private int actualParameterPosition = 0;

		StaticMethodVisitor(Method method, StaticClassVisitor scv,
				int parameterCount) {

			this.scv = scv;
			this.parameterCount = parameterCount;
			annotations = method.getParameterAnnotations();
			parameterTypes = method.getParameterTypes();
		}

		@Override
		public void visitMethodInsn(int opcode, String owner, String name,
				String desc) {
		}

		@Override
		public void visitLocalVariable(String name, String desc,
				String signature, Label start, Label end, int index) {

			if ("this".equals(name))
				return;

			Parameter[] parameters = scv.getParameters();

			if (parameters == null)
				return;

			if (parameterCount == actualParameterPosition)
				return;

			int i = actualParameterPosition++;

			Parameter parameter = new Parameter(name, parameterTypes[i],
					annotations[i]);

			parameters[i] = parameter;
		}

		@Override
		public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
			return null;
		}

		@Override
		public AnnotationVisitor visitAnnotationDefault() {
			return null;
		}

		@Override
		public void visitAttribute(Attribute attr) {
		}

		@Override
		public void visitCode() {
		}

		@Override
		public void visitEnd() {
		}

		@Override
		public void visitFieldInsn(int opcode, String owner, String name,
				String desc) {
		}

		@Override
		public void visitFrame(int type, int nLocal, Object[] local,
				int nStack, Object[] stack) {
		}

		@Override
		public void visitIincInsn(int var, int increment) {
		}

		@Override
		public void visitInsn(int opcode) {
		}

		@Override
		public void visitIntInsn(int opcode, int operand) {
		}

		@Override
		public void visitJumpInsn(int opcode, Label label) {
		}

		@Override
		public void visitLabel(Label label) {
		}

		@Override
		public void visitLdcInsn(Object cst) {
		}

		@Override
		public void visitLineNumber(int line, Label start) {
		}

		@Override
		public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
		}

		@Override
		public void visitMaxs(int maxStack, int maxLocals) {
		}

		@Override
		public void visitMultiANewArrayInsn(String desc, int dims) {
		}

		@Override
		public AnnotationVisitor visitParameterAnnotation(int parameter,
				String desc, boolean visible) {
			return null;
		}

		@Override
		public void visitTableSwitchInsn(int min, int max, Label dflt,
				Label[] labels) {
		}

		@Override
		public void visitTryCatchBlock(Label start, Label end, Label handler,
				String type) {
		}

		@Override
		public void visitTypeInsn(int opcode, String type) {
		}

		@Override
		public void visitVarInsn(int opcode, int var) {
		}

	}

}
